package com.joinway.framework.support.logging;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.util.ClassUtils;

import com.joinway.framework.bean.BaseBean;
import com.joinway.framework.bean.TraceableBean;
import com.joinway.framework.bean.exception.InternalException;
import com.joinway.framework.bean.logging.annotation.InputLog;
import com.joinway.framework.bean.logging.annotation.LogIgnore;
import com.joinway.framework.bean.logging.annotation.LogMask;
import com.joinway.framework.bean.logging.annotation.OutputLog;
import com.joinway.framework.bean.logging.type.LogFormat;
import com.joinway.framework.bean.logging.type.LogLevel;
import com.joinway.framework.bean.logging.type.LogMaskType;
import com.joinway.framework.bean.utils.LoggingHelper;
import com.joinway.framework.core.utils.AopHelper;
import com.joinway.framework.extension.net.json.JsonConverter;
import com.joinway.framework.support.logging.utils.LoggingUtils;
import com.joinway.framework.support.utils.InterceptorHelper;

public class LogAspect {
	private final static Logger log = LoggerFactory.getLogger(LogAspect.class);
	
	/**
	 * 轻量模式，不检查InputLog和OutputLog注解
	 */
	private boolean light = true;
	
	private List<String> ignoredPackages;
	
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
		Method method = AopHelper.getMethod(point);
        LogIgnore ignore = method.getAnnotation(LogIgnore.class);

        if (ignore == null && method != null) {
            logInputMessage(point, method);
        }

        Object output = null;
        
        try{
        	output = point.proceed();
        	
        }catch(InternalException e){
        	throw e;
        	
        }catch(Throwable t){
        	throw t;
        	
        }
        
        if (ignore == null && method != null) {
            logOutputMessage(point, method, output);
        }

        return output;
    }

    protected void logInputMessage(JoinPoint point, Method method) {
        boolean mdc = false;
        
        try {
            InputLog ann = method.getAnnotation(InputLog.class);

            LogLevel level = ann == null ? LogLevel.Info : ann.value();
            
            String packageName = ClassUtils.getPackageName(point.getTarget().getClass()); 
            
            // check whether need to log input arguments
            if (light || ann != null) {
                // if LogIgnore applied on method, will ignored whole method
                // log
                Object[] args = point.getArgs();
                Annotation[][] anns = method.getParameterAnnotations();

                if(ArrayUtils.isNotEmpty(args)){
	                for (int i = 0; i < args.length; i++) {
	                    LogMask mask = null;
	
	                    if (anns != null) {
	                        LogIgnore ignore = InterceptorHelper.getParameterAnnotation(anns[i], LogIgnore.class);
	                        if (ignore != null) {
	                            continue; // ignore output the parameter log
	                        }
	
	                        mask = InterceptorHelper.getParameterAnnotation(anns[i], LogMask.class);
	                    }
	
	//                    StringBuilder text = new StringBuilder();
	
	                    // build object log
	                    Object body = buildLogMessageBody(args[i], mask);
	
	                    String logPrefix = getInputPrefix(point, method, i);
	                    String loggerText = LoggingUtils.getLoggerText(point, method);
	                    
	                    LoggingUtils.putLoggerText(loggerText);
	                    mdc = true;
	                    
	                    if(ignoredPackages == null || !ignoredPackages.contains(packageName)){
	                    	outputMessage(logPrefix, body, level, null);
	                    }
	                }
                }else{
                	String logPrefix = getInputPrefix(point, method, null);
                	String loggerText = LoggingUtils.getLoggerText(point, method);
                    
                    LoggingUtils.putLoggerText(loggerText);
                    mdc = true;
                    
                    if(ignoredPackages == null || !ignoredPackages.contains(packageName)){
                    	outputMessage(logPrefix, "", level, null);
                    }
                }
            }
        } catch (Throwable t) {
            log.warn("exception occured when logging input {}", t.toString());
        }finally{
            if(mdc){
            	LoggingUtils.removeLoggerText();
            }
        }
    }

    protected void logOutputMessage(JoinPoint jp, Method method, Object output) {
        boolean mdc = false;
        
        try {
            OutputLog ann = method.getAnnotation(OutputLog.class);

            LogLevel level = ann == null ? LogLevel.Debug : ann.value();
            LogFormat format = ann == null ? LogFormat.Reflection : ann.format();
            
            if (light || ann != null) {
//                StringBuilder text = new StringBuilder();

                // build object log
                Object body = buildLogMessageBody(output, null);

                String logPrefix = getOutputPrefix(jp, method);
                String logText = LoggingUtils.getLoggerText(jp, method);
                
                LoggingUtils.putLoggerText(logText);
                mdc = true;
                
                outputMessage(logPrefix, body, level, format);
            }
        } catch (Throwable t) {
            log.warn("exception occured when logging output {}", t.toString());
        }finally{
            if(mdc){
            	LoggingUtils.removeLoggerText();
            }
        }
    }

    protected Object buildLogMessageBody(Object arg, LogMask mask) {
        Object body = null;

        if (arg != null && LoggingHelper.isSimpleType(arg.getClass())) {
        	StringBuilder text = new StringBuilder();
            // for simple types, log as string way
            if (mask != null) {
                if (mask.value() == LogMaskType.SimpleMask) {
                    // for simple mask, mark some chars
                    text = text.append(LoggingHelper.markString(String.valueOf(arg), LogMaskType.SimpleMask.getMask()));
                } else {
                    // for full mask, mask all chars with mask chars
                    text = text.append(LoggingHelper.markFullString(String.valueOf(arg), LogMaskType.FullMask.getMask()));
                }
            } else {
                // no mask, output object directly
//            	if(arg instanceof BaseBean){
//            		text = text.append(String.valueOf(arg));
//            	}else{
            		text = text.append(LoggingUtils.convertObjectToString(arg));
//            	}
            }
            
            body = text;
        } else {
            // output object
        	if(arg instanceof BaseBean){
        		body = arg;
        	}else if(arg instanceof TraceableBean){
        		body = LoggingUtils.convertObjectToString(arg);
        	}else{
        		body = LoggingUtils.convertObjectToString(arg);
        	}
        }
        return body;
    }


//    protected String getLoggerText(JoinPoint jp, Method method){
//        return StringUtils.join(new Object[] {jp.getTarget().getClass().getCanonicalName(), ".", method.getName()});
//    }
    
    protected String getInputPrefix(JoinPoint jp, Method method, Integer index) {
    	if(index != null){
    		return StringUtils.join(new Object[] { "input args[", index, "]->" });
    	}else{
    		return "input args ->";
    	}
    }


    protected String getOutputPrefix(JoinPoint jp, Method method) {
        return StringUtils.join(new Object[] { "output==>"});
    }


    protected void outputMessage(String prefix, Object text, LogLevel level, LogFormat format) throws Exception {
    	Object output = format == LogFormat.Json ? JsonConverter.objectToJson(text, true) : text;
    	
        if (level == LogLevel.Info) {
            log.info("{}[{}]", prefix, output);
        } else if (level == LogLevel.Debug) {
            log.debug("{}[{}]", prefix, output);
        } else if (level == LogLevel.Warn) {
            log.warn("{}[{}]", prefix, output);
        } else if (level == LogLevel.Error) {
            log.error("{}[{}]", prefix, output);
        } else if (level == LogLevel.Trace) {
            log.trace("{}[{}]", prefix, output);
        }
    }

	public boolean isLight() {
		return light;
	}

	public void setLight(boolean light) {
		this.light = light;
	}

	public List<String> getIgnoredPackages() {
		return ignoredPackages;
	}

	public void setIgnoredPackages(List<String> ignoredPackages) {
		this.ignoredPackages = ignoredPackages;
	}

//	@Override
//	public int getOrder() {
//		return InterceptorOrderConstants.LOGGING;
//	}

}
